#!/usr/bin/env ruby

# texrb - a simple Ruby preprocessor for LaTeX
# Copyright (C) 2010-2011, Richard Cook
# http://texrb.googlecode.com/
# Licensed under GNU/GPLv3 licence (http://www.gnu.org/licenses/gpl-3.0.html)

require 'erb'
require 'fileutils'
require 'tempfile'

module Constants
  OUTPUT_SWITCH = 'output'
  ARGS_SWITCH = 'args'
  CODE_BEGIN_MARKER = '<?'
  CODE_END_MARKER = '?>'
end

module Helpers
  def self.pluralize(count, text)
    count == 1 ? "1 #{text}" : "#{count} #{text}s"
  end

  def self.parse_define(str)
    if str =~ /^-D([A-Za-z][A-Za-z0-9_]*)$/
      [$1, true]
    elsif str =~ /^-D([A-Za-z][A-Za-z0-9_]*)=(false|true)$/
      [$1, $2 == 'true']
    elsif str =~ /^-D([A-Za-z][A-Za-z0-9_]*)=(.*)$/
      [$1, $2]
    else
      nil
    end
  end

  def self.write_on_change(output_file_name, content)
    begin
      f = Tempfile.new('texrb-script-')
      f.write(content)
      f.close
      if !File.exist?(output_file_name) || !FileUtils.compare_file(f.path, output_file_name)
        FileUtils.mv(f.path, output_file_name)
      end
    ensure
      f.delete if f
    end
  end
end

class Template
  class Container
    def initialize(context)
      @context = context
    end

    def get_binding
      binding
    end
  end

  def initialize(text)
    @text = text
  end

  def render(context)
    container = Container.new(context)
    erb.result(container.get_binding)
  end

  def erb
    @erb ||= ERB.new(@text, nil, '-')
  end
  private :erb
end

class Context
  def initialize(defines)
    @defines = defines
  end

  def render(input_file_name)
    input_file_name = "#{input_file_name}.texrb" if File.extname(input_file_name).empty?
    template = Template.new(File.read(input_file_name))
    template.render(self)
  end

  def method_missing(sym, *args, &block)
    name = sym.to_s
    return @defines[name] if @defines.include?(name)
    raise RuntimeError.new("Method #{sym} not defined")
  end

  def defines?(name)
    @defines.include?(name)
  end
end

args = ARGV.clone

input_file_name = nil
output_file_name = nil
arguments = nil
defines = {}
error_messages = []
while args.size > 0
  arg = args.slice!(0)

  prefix = "--#{Constants::OUTPUT_SWITCH}="
  if arg.start_with?(prefix)
    if !output_file_name.nil?
      error_messages << 'Output file name must be specified exactly once with --output switch.'
      break
    end
    output_file_name = arg[prefix.size..arg.size - 1]
    next
  end

  prefix = "--#{Constants::ARGS_SWITCH}="
  if arg.start_with?(prefix)
    if !arguments.nil?
      error_messages << 'Arguments must be specified no more than once with --args switch.'
      break
    end
    arguments = arg[prefix.size..arg.size - 1]
    if arguments =~ /^"(.*)"$/
      arguments = $1
    else
      error_messages << "Value #{arguments} passed via --args switch is not well-formed."
      arguments = nil
    end
    next
  end

  if d = Helpers.parse_define(arg)
    name, value = d
    if defines.include?(name)
      error_messages << "Can define value #{name} at most once"
      break
    end
    defines[name] = value
    next
  end

  if !input_file_name.nil?
    error_messages << 'Input file name must be specified exactly once.'
    break
  end
  input_file_name = arg
  next
end
error_messages << 'Output file name must be specified exactly once with --output switch.' if output_file_name.nil?
error_messages << 'Input file name must be specified exactly once.' if input_file_name.nil?

if error_messages.size > 0
  puts
  puts 'texrb - a simple Ruby preprocessor for LaTeX'
  puts 'Copyright (C) 2010-2011, Richard Cook'
  puts 'http://texrb.googlecode.com/'
  puts 'Licensed under GNU/GPLv3 licence (http://www.gnu.org/licenses/gpl-3.0.html)'
  puts
  puts "#{Helpers::pluralize(error_messages.size, 'error')}:"
  error_messages.each do |error_message|
    puts "  * #{error_message}"
  end
  puts
  exit 1
end

context = Context.new(defines)
template = Template.new(File.read(input_file_name))
output = template.render(context)
Helpers::write_on_change(output_file_name, output)
